# socket 网络编程，socket属于底层实现，实际使用多为高层协议如http、rpc等
# c/s模式socket通信代码socket_demo 的package模块中
# 

import socket
import threading
import logging

log_format = '%(asctime)s %(threadName)s %(thread)d: %(message)s'
logging.basicConfig(format=log_format, level=logging.INFO)

class ChatServer:
    ''' tcp socket chat server demo '''
    def __init__(self, ip='localhost', port=9999):
        self.addr = (ip, port)
        self.socket = socket.socket()
        self.clients = {} # 存储socket连接
        self.event = threading.Event()

    def start(self):
        self.socket.bind(self.addr)
        self.socket.listen()
        threading.Thread(target=self.accept, name='accept-thread').start()
        logging.info('server running..')

    def accept(self):
        i = 0
        while not self.event.is_set():
            sock, raddr = self.socket.accept()
            self.clients[sock.getpeername()] = sock # getpeername和raddr都是远程地址，是一个元组(ip,port)
            logging.info(f'new connection => {raddr}')
            threading.Thread(target=self.recv, name='recv-thread-' + str(i), args=(sock,)).start()
            i += 1

    def recv(self, sock):
        while not self.event.is_set():
            try:
                data = sock.recv(1024) # 客户端关闭会抛出异常
                logging.info(f'recv msg => {data.decode()}') # 二进制转化为字符串
            except Exception as e:
                logging.error(e)
                data = b'exit'

            if data == b'exit':
                logging.info(f'close -> {self.clients.pop(sock.getpeername())}') # 弹出socket
                sock.close()
                break

            sock.send('ack'.encode()) # 字符串转二进制

            for r,s in self.clients.items():
                if s != sock:
                    logging.info(f'send to {r}')
                    s.send(f'{r} say: {data.decode()}'.encode())

    def stop(self):
        for s in self.clients.values():
            logging.info(f'close connection {s.getpeername()}') # getpeername 远程地址
            s.close()
        self.socket.close()
        self.event.set() 

# 一切接文件，可以将sock做成一个文件 makefile
# help(socket.socket.makefile)
class ChatServer1:
    ''' tcp socket chat server demo '''
    def __init__(self, ip='localhost', port=9999):
        self.addr = (ip, port)
        self.socket = socket.socket()
        self.clients = {} # 存储socket连接
        self.event = threading.Event()

    def start(self):
        self.socket.bind(self.addr)
        self.socket.listen()
        threading.Thread(target=self.accept, name='accept-thread').start()
        logging.info('server running..')

    def accept(self):
        i = 0
        while not self.event.is_set():
            try:
                s, raddr = self.socket.accept()
                f = s.makefile(mode='rw') # 读写sock文件
            except OSError as ose:
                logging.error(ose)
                break
            self.clients[raddr] = f # getpeername和raddr都是远程地址，是一个元组(ip,port)
            logging.info(f'new connection => {raddr}')
            threading.Thread(target=self.recv, name='recv-thread-' + str(i), args=(f, raddr)).start()
            i += 1

    def recv(self, f, raddr):
        while not self.event.is_set():
            try:
                data = f.readline() # 读取一行
                # data = f.read(10) # 读取十个字符
                logging.info(f'recv msg => {data}') # 字符串
            except Exception as e:
                logging.error(e)
                data = 'exit'

            if data == 'exit':
                logging.info(f'close -> {self.clients.pop(raddr)}') # 弹出file
                f.close()
                break

            f.write('ack') # 写入
            f.flush()

            for r,s in self.clients.items():
                if s != f:
                    logging.info(f'send to {raddr}')
                    s.write(f'{raddr} say: {data}')

    def stop(self):
        for f in self.clients.values():
            logging.info(f'close file {f}') #
            f.close()
        self.socket.close()
        self.event.set() 


def main(s):
    s.start()

    # 客户端使用02里的client演示
    while True:
        cmd = input('>>>')
        if cmd == 'exit':
            s.stop()
            import time
            logging.info('closing...')
            time.sleep(3) # 等待3秒，socket关闭较慢
            break
        print(threading.enumerate()) # 枚举当前线程数

if __name__ == '__main__':
    # main(ChatServer())
    main(ChatServer1())

